"""
OpenCV元数据提取器

使用OpenCV提取视频的视觉特征元数据。
"""

import cv2
import numpy as np
import os
from typing import Dict, Any, Union, List
from datetime import datetime
from pathlib import Path
import logging

from src.core.di import Injectable, Inject
from .base import MetadataExtractor, VideoMetadata, AudioMetadata


@Injectable(scope="singleton")
class OpenCVMetadataExtractor(MetadataExtractor):
    """
    OpenCV元数据提取器
    
    使用OpenCV提取视频的视觉特征和技术元数据。
    """
    
    def __init__(self,
                 config: Dict[str, Any] = Inject("config"),
                 logger: logging.Logger = Inject("logger")):
        super().__init__()
        self.config = config
        self.logger = logger
        
        # OpenCV配置
        self.opencv_config = config.get("opencv", {})
        self.sample_frames = self.opencv_config.get("metadata_sample_frames", 10)
        
        # 支持的视频格式
        self.video_formats = [
            "mp4", "avi", "mov", "mkv", "webm", "flv", "wmv", "m4v"
        ]
    
    def get_supported_formats(self) -> List[str]:
        """获取支持的文件格式"""
        return self.video_formats
    
    async def extract_metadata(self, file_path: str, **kwargs) -> Union[VideoMetadata, AudioMetadata]:
        """提取文件元数据"""
        file_type = self.get_file_type(file_path)
        
        if file_type == "video":
            return await self.extract_video_metadata(file_path, **kwargs)
        else:
            raise ValueError(f"OpenCV提取器不支持音频文件: {file_path}")
    
    async def extract_video_metadata(self, video_path: str, **kwargs) -> VideoMetadata:
        """提取视频元数据"""
        self.logger.info(f"使用OpenCV提取视频元数据: {video_path}")
        
        if not os.path.exists(video_path):
            raise FileNotFoundError(f"视频文件不存在: {video_path}")
        
        # 打开视频
        cap = cv2.VideoCapture(video_path)
        if not cap.isOpened():
            raise RuntimeError(f"无法打开视频文件: {video_path}")
        
        try:
            # 基本文件信息
            file_stat = os.stat(video_path)
            path_obj = Path(video_path)
            
            metadata = VideoMetadata(
                filename=path_obj.name,
                filepath=str(path_obj.absolute()),
                file_size=file_stat.st_size,
                modification_time=datetime.fromtimestamp(file_stat.st_mtime)
            )
            
            # 从OpenCV获取基本视频信息
            metadata.width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
            metadata.height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
            metadata.fps = cap.get(cv2.CAP_PROP_FPS)
            metadata.frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
            
            # 计算时长
            if metadata.fps > 0:
                metadata.duration = metadata.frame_count / metadata.fps
            
            # 计算宽高比
            if metadata.width and metadata.height:
                from math import gcd
                g = gcd(metadata.width, metadata.height)
                metadata.aspect_ratio = f"{metadata.width//g}:{metadata.height//g}"
            
            # 获取编码器信息（如果可用）
            fourcc = int(cap.get(cv2.CAP_PROP_FOURCC))
            if fourcc:
                metadata.video_codec = self._fourcc_to_string(fourcc)
            
            # 提取视觉特征元数据
            visual_metadata = await self._extract_visual_features(cap)
            metadata.custom_metadata.update(visual_metadata)
            
            # 添加提取器信息
            metadata.custom_metadata.update({
                "extractor": "opencv",
                "extraction_time": datetime.now().isoformat(),
                "opencv_version": cv2.__version__
            })
            
            self.logger.info(f"OpenCV视频元数据提取完成: {metadata.filename}")
            return metadata
            
        finally:
            cap.release()
    
    async def extract_audio_metadata(self, audio_path: str, **kwargs) -> AudioMetadata:
        """提取音频元数据（OpenCV不支持）"""
        raise NotImplementedError("OpenCV提取器不支持音频文件")
    
    async def _extract_visual_features(self, cap: cv2.VideoCapture) -> Dict[str, Any]:
        """提取视觉特征元数据"""
        features = {
            "visual_features": {},
            "color_analysis": {},
            "motion_analysis": {},
            "quality_metrics": {}
        }
        
        # 采样帧进行分析
        total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
        sample_indices = self._get_sample_indices(total_frames, self.sample_frames)
        
        frames = []
        brightness_values = []
        contrast_values = []
        
        for frame_idx in sample_indices:
            cap.set(cv2.CAP_PROP_POS_FRAMES, frame_idx)
            ret, frame = cap.read()
            
            if ret:
                frames.append(frame)
                
                # 计算亮度和对比度
                gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
                brightness = np.mean(gray)
                contrast = np.std(gray)
                
                brightness_values.append(brightness)
                contrast_values.append(contrast)
        
        if frames:
            # 颜色分析
            features["color_analysis"] = self._analyze_colors(frames)
            
            # 质量指标
            features["quality_metrics"] = {
                "average_brightness": float(np.mean(brightness_values)),
                "brightness_std": float(np.std(brightness_values)),
                "brightness_range": [float(np.min(brightness_values)), float(np.max(brightness_values))],
                "average_contrast": float(np.mean(contrast_values)),
                "contrast_std": float(np.std(contrast_values)),
                "contrast_range": [float(np.min(contrast_values)), float(np.max(contrast_values))]
            }
            
            # 运动分析
            if len(frames) > 1:
                features["motion_analysis"] = self._analyze_motion(frames)
            
            # 视觉特征
            features["visual_features"] = self._extract_visual_descriptors(frames[0])
        
        return features

    def _get_sample_indices(self, total_frames: int, sample_count: int) -> List[int]:
        """获取采样帧索引"""
        if total_frames <= sample_count:
            return list(range(total_frames))

        # 均匀采样
        step = total_frames / sample_count
        indices = [int(i * step) for i in range(sample_count)]
        return indices

    def _fourcc_to_string(self, fourcc: int) -> str:
        """将FOURCC代码转换为字符串"""
        try:
            return "".join([chr((fourcc >> 8 * i) & 0xFF) for i in range(4)])
        except:
            return f"FOURCC_{fourcc}"

    def _analyze_colors(self, frames: List[np.ndarray]) -> Dict[str, Any]:
        """分析颜色特征"""
        color_stats = {
            "dominant_colors": [],
            "color_distribution": {},
            "saturation_stats": {},
            "hue_stats": {}
        }

        all_hues = []
        all_saturations = []

        for frame in frames[:3]:  # 只分析前3帧以节省时间
            # 转换到HSV色彩空间
            hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)

            # 提取色调和饱和度
            hue = hsv[:, :, 0].flatten()
            saturation = hsv[:, :, 1].flatten()

            all_hues.extend(hue)
            all_saturations.extend(saturation)

        if all_hues:
            color_stats["hue_stats"] = {
                "mean": float(np.mean(all_hues)),
                "std": float(np.std(all_hues)),
                "range": [float(np.min(all_hues)), float(np.max(all_hues))]
            }

            color_stats["saturation_stats"] = {
                "mean": float(np.mean(all_saturations)),
                "std": float(np.std(all_saturations)),
                "range": [float(np.min(all_saturations)), float(np.max(all_saturations))]
            }

        return color_stats

    def _analyze_motion(self, frames: List[np.ndarray]) -> Dict[str, Any]:
        """分析运动特征"""
        motion_stats = {
            "motion_detected": False,
            "average_motion": 0.0,
            "motion_variance": 0.0,
            "motion_timeline": []
        }

        motion_values = []

        for i in range(1, len(frames)):
            prev_gray = cv2.cvtColor(frames[i-1], cv2.COLOR_BGR2GRAY)
            curr_gray = cv2.cvtColor(frames[i], cv2.COLOR_BGR2GRAY)

            # 计算帧差
            diff = cv2.absdiff(prev_gray, curr_gray)
            motion_value = np.mean(diff) / 255.0  # 归一化到0-1

            motion_values.append(motion_value)
            motion_stats["motion_timeline"].append(float(motion_value))

        if motion_values:
            motion_stats["average_motion"] = float(np.mean(motion_values))
            motion_stats["motion_variance"] = float(np.var(motion_values))
            motion_stats["motion_detected"] = motion_stats["average_motion"] > 0.1

        return motion_stats

    def _extract_visual_descriptors(self, frame: np.ndarray) -> Dict[str, Any]:
        """提取视觉描述符"""
        descriptors = {
            "edge_density": 0.0,
            "texture_complexity": 0.0,
            "sharpness": 0.0
        }

        try:
            gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

            # 边缘密度
            edges = cv2.Canny(gray, 50, 150)
            edge_density = np.sum(edges > 0) / (edges.shape[0] * edges.shape[1])
            descriptors["edge_density"] = float(edge_density)

            # 纹理复杂度（使用拉普拉斯算子）
            laplacian = cv2.Laplacian(gray, cv2.CV_64F)
            texture_complexity = np.var(laplacian)
            descriptors["texture_complexity"] = float(texture_complexity)

            # 清晰度（使用梯度幅度）
            grad_x = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3)
            grad_y = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3)
            sharpness = np.mean(np.sqrt(grad_x**2 + grad_y**2))
            descriptors["sharpness"] = float(sharpness)

        except Exception as e:
            self.logger.warning(f"提取视觉描述符时出错: {e}")

        return descriptors

    # 实现插件接口方法
    async def classify(self, input_data: Any, **kwargs) -> Any:
        """分类数据（不适用于元数据提取器）"""
        raise NotImplementedError("元数据提取器不支持分类功能")

    async def output(self, data: Any, **kwargs) -> Any:
        """输出数据（不适用于元数据提取器）"""
        raise NotImplementedError("元数据提取器不支持输出功能")
